<template>
  <div v-if="step === 0" class="intro">
    <div>
      横向卡片最大平铺排数
      <input
        v-model="option.x"
        min="2"
        max="10"
        type="range"
      > {{ option.x }}
    </div>
    <div>
      纵向卡片最大平铺排数
      <input
        v-model="option.y"
        min="2"
        max="10"
        type="range"
      > {{ option.y }}
    </div>
    <div>
      卡片最大堆叠层数
      <input
        v-model="option.z"
        min="2"
        max="10"
        type="range"
      > {{ option.z }}
    </div>
    <div>
      卡片密度
      <input
        v-model="option.cardRandom"
        min="0"
        max="1"
        step="0.1"
        type="range"
      >
      {{ option.cardRandom }}
    </div>
    <div>
      最大卡片种类
      <input
        v-model="option.maxCardType"
        min="3"
        max="14"
        step="1"
        type="range"
      >
      {{ option.maxCardType }}
    </div>
    <br>
    <button @click="startGame">开始游戏</button>
  </div>
  <div v-else-if="step === 2" class="intro">
    <h1>{{ result ? "You Win！🎉" : "You Lose!😢" }}</h1>
    <button @click="rePlay">再来一轮</button>
    <button @click="setGame">难度调节</button>
  </div>
  <div v-else class="box">
    <div class="card-wrap" :style="cardWrapStyle">
      <div
        v-for="item in cardItemList"
        :key="item.key"
        :class="{'item-cover': item.cover}"
        class="card-item"
        :style="item.style"
        @click="clickCard(item)"
      >
        {{ item.content }}
      </div>
      <div v-for="item in penddingList" :key="item.key" class="card-item" :style="item.style">
        {{ item.content }}
      </div>
      <div v-for="item in clearList" :key="item.key" class="card-item clear-item" :style="item.style">
        {{ item.content }}
      </div>
      <div v-for="item in saveList" :key="item.key" class="card-item" :style="item.style" @click="clickSaveCard(item)">
        {{ item.content }}
      </div>
      <p class="card-tips">
        剩余空位:{{ 7 - penddingList.length }}/7；已消除:{{ clearList.length }}/{{
          cardItemList.length + penddingList.length + saveList.length + clearList.length
        }}
      </p>
    </div>
    <div class="tools">
      道具：
      <button :disabled="!tools.save" @click="saveCard">取出3个卡片</button>
      <button :disabled="!tools.rand" @click="randCard">随机</button>
      <button @click="rePlay">再来一轮</button>
    </div>
  </div>
</template>

<script>

class CardItem {
    static x = 20;
    static y = 21;
    static colorType = {
      1: { background: '#FFB7DD' },
      2: { background: '#FFCCCC' },
      3: { background: '#FFC8B4' },
      4: { background: '#FFDDAA' },
      5: { background: '#FFEE99' },
      6: { background: '#FFFFBB' },
      7: { background: '#EEFFBB' },
      8: { background: '#CCFF99' },
      9: { background: '#99FF99' },
      10: { background: '#BBFFEE' },
      11: { background: '#AAFFEE' },
      12: { background: '#99FFFF' },
      13: { background: '#CCEEFF' },
      14: { background: '#CCDDFF' }
    };
    static contentType = {
      1: '🥕',
      2: '✂️',
      3: '🥦',
      4: '🥛',
      5: '🌊',
      6: '🧤',
      7: '🧵',
      8: '🌱',
      9: '🔨',
      10: '🌽',
      11: '🌾',
      12: '🐑',
      13: '🪵',
      14: '🔥'
    };
    constructor({ x, y, z, key }) {
      this.x = x
      this.y = y
      this.z = z
      this.key = key
      const offset = z * 0
      this.val = key
      this.style = {
        top: y * CardItem.y + offset + 'px',
        left: x * CardItem.x + offset + 'px',
        width: CardItem.x * 2 - 2 + 'px',
        height: CardItem.y * 2 - 8 + 'px'
      }
    }

    setValue(val) {
      this.val = val
      this.content = CardItem.contentType[val]
      Object.assign(this.style, CardItem.colorType[val])
    }
}

export default {
  data() {
    return {
      option: {
        x: 6,
        y: 4,
        z: 8,
        cardRandom: 0.2,
        maxCardType: 11
      },
      step: 0,
      win: false,
      cardMap: [],
      cardItemList: [],
      penddingList: [],
      clearList: [],
      saveList: [],
      calcValueList: [],
      maxWidth: 0,
      maxHeight: 0,
      tools: {
        save: true,
        rand: true
      },
      timer: 0
    }
  },
  computed: {
    cardWrapStyle() {
      return {
        width: (this.maxWidth + 2) * CardItem.x + 'px',
        height: (this.maxHeight + 1) * CardItem.y + 'px'
      }
    },
    leftOffset() {
      const wrapWidth = (this.maxWidth + 2) * CardItem.x
      return (wrapWidth - 7 * CardItem.x * 2) / 2
    }
  },
  methods: {
    randCard() {
      if (!this.tools.rand) {
        return
      }
      this.tools.rand = false
      const length = this.cardItemList.length
      this.cardItemList.forEach(item => {
        const randNum = Math.floor(length * Math.random())
        const newItem = this.cardItemList[randNum]
        let temp
        temp = item.style.left
        item.style.left = newItem.style.left
        newItem.style.left = temp
        temp = item.style.top
        item.style.top = newItem.style.top
        newItem.style.top = temp
        temp = item.x
        item.x = newItem.x
        newItem.x = temp
        temp = item.y
        item.y = newItem.y
        newItem.y = temp
        temp = item.z
        item.z = newItem.z
        newItem.z = temp
      })

      this.cardItemList.sort((a, b) => a.z - b.z)
      this.calcCover()
    },
    saveCard() {
      if (!this.tools.save) {
        return false
      }
      this.tools.save = false
      this.saveList = this.penddingList.slice(0, 3)
      setTimeout(() => {
        this.saveList.forEach((item, index) => {
          item.style.top = '110%'
          item.style.left = this.leftOffset + index * CardItem.x * 2 + 'px'
          this.calcValueList[item.val]--
        })
      }, 0)
      this.penddingList = this.penddingList.slice(3)
      this.penddingList.forEach((item, index) => {
        item.style.top = '160%'
        item.style.left = this.leftOffset + index * CardItem.x * 2 + 'px'
      })
    },
    initGame() {
      this.step = 1
      this.getMap(this.option)
      this.penddingList = []
      this.clearList = []
      this.saveList = []
      this.tools.save = true
      this.tools.rand = true
      this.setCardValue({ maxCardType: Number(this.option.maxCardType) })
      this.calcCover()
    },
    // 表示地图最大为 x * y 张牌，最多有 z 层
    getMap({ x, y, z, cardRandom } = {}) {
      this.maxWidth = (x - 1) * 2
      this.maxHeight = (y - 1) * 2 + 1
      const cardMap = new Array(z)
      const cardItemList = []
      let key = 0
      // 地图初始化
      for (let k = 0; k < z; k++) {
        cardMap[k] = new Array(this.maxHeight)
        for (let i = 0; i <= this.maxHeight; i++) {
          cardMap[k][i] = new Array(this.maxWidth).fill(0)
        }
      }
      for (let k = 0; k < z; k++) {
        const shrink = Math.floor((z - k) / 3)
        // 行
        for (let i = shrink; i < this.maxHeight - shrink; i++) {
          // 列，对称设置
          const mid = Math.ceil((this.maxWidth - shrink) / 2)
          for (let j = shrink; j <= mid; j++) {
            let canSetCard = true
            if (j > 0 && cardMap[k][i][j - 1]) {
              // 左边不能有牌
              canSetCard = false
            } else if (i > 0 && cardMap[k][i - 1][j]) {
              // 上边不能有牌
              canSetCard = false
            } else if (i > 0 && j > 0 && cardMap[k][i - 1][j - 1]) {
              // 左上不能有牌
              canSetCard = false
            } else if (i > 0 && cardMap[k][i - 1][j + 1]) {
              // 右上不能有牌
              canSetCard = false
            } else if (k > 0 && cardMap[k - 1][i][j]) {
              // 正底不能有牌
              canSetCard = false
            } else if (Math.random() >= cardRandom) {
              canSetCard = false
            }
            if (canSetCard) {
              key++
              const cardItem = new CardItem({ x: j, y: i, z: k, key })
              cardMap[k][i][j] = cardItem
              cardItemList.push(cardItem)
              // 对称放置
              if (j < mid) {
                key++
                const cardItem = new CardItem({
                  x: this.maxWidth - j,
                  y: i,
                  z: k,
                  key
                })
                cardMap[k][i][j] = cardItem
                cardItemList.push(cardItem)
              }
            }
          }
        }
      }
      cardItemList.reverse()
      for (let i = 1; i <= key % 3; i++) {
        const clearItem = cardItemList.pop()
        cardMap[clearItem.z][clearItem.y][clearItem.x] = 0
      }
      cardItemList.reverse()
      this.cardMap = cardMap
      this.cardItemList = cardItemList
    },
    setCardValue({ maxCardType } = {}) {
      // 卡片种类
      const valStack = new Array(maxCardType)
      this.calcValueList = new Array(maxCardType + 1).fill(0)
      // 给卡片设置值
      this.cardItemList.forEach(item => {
        const value = Math.ceil(Math.random() * maxCardType)
        if (valStack[value]) {
          valStack[value].push(item)
          if (valStack[value].length === 3) {
            valStack[value].forEach(item => {
              item.setValue(value)
            })
            valStack[value] = null
          }
        } else {
          valStack[value] = [item]
        }
      })

      let count = 2
      // console.log(valStack)
      valStack.forEach(list => {
        list &&
                    list.forEach(item => {
                      count++
                      item.setValue(Math.floor(count / 3))
                    })
      })
    },
    // 计算遮挡关系
    calcCover() {
      // 构建一个遮挡 map
      const coverMap = new Array(this.maxHeight)
      for (let i = 0; i <= this.maxHeight; i++) {
        coverMap[i] = new Array(this.maxWidth).fill(false)
      }

      // 从后往前，后面的层数高
      for (let i = this.cardItemList.length - 1; i >= 0; i--) {
        const item = this.cardItemList[i]
        const { x, y } = item
        if (coverMap[y][x]) {
          item.cover = true
        } else if (coverMap[y][x + 1]) {
          item.cover = true
        } else if (coverMap[y + 1][x]) {
          item.cover = true
        } else if (coverMap[y + 1][x + 1]) {
          item.cover = true
        } else {
          item.cover = false
        }
        coverMap[y][x] = true
        coverMap[y + 1][x] = true
        coverMap[y][x + 1] = true
        coverMap[y + 1][x + 1] = true
      }
    },
    clickSaveCard(item) {
      this.cardItemList.push(item)
      const index = this.saveList.indexOf(item)
      this.saveList = this.saveList
        .slice(0, index)
        .concat(this.saveList.slice(index + 1))
      this.clickCard(item)
    },
    removeThree() {
      this.penddingList.some(item => {
        if (this.calcValueList[item.val] === 3) {
          this.penddingList.forEach(newItem => {
            if (newItem.val === item.val) {
              this.clearList.push(newItem)
            }
          })
          setTimeout(() => {
            this.clearList.forEach((item) => {
              item.style.left = this.leftOffset - 60 + 'px'
            })
          }, 300)

          this.penddingList = this.penddingList.filter(newItem => {
            return newItem.val !== item.val
          })
          this.penddingList.forEach((item, index) => {
            item.style.top = '160%'
            item.style.left = this.leftOffset + index * CardItem.x * 2 + 'px'
          })
          this.calcValueList[item.val] = 0
          if (this.cardItemList.length === 0) {
            this.step = 2
            this.result = true
          }
        }
      })

      if (this.penddingList.length >= 7) {
        this.step = 2
        this.result = false
      }
    },
    // 点击卡片
    clickCard(item) {
      clearTimeout(this.timer)
      this.removeThree()
      this.penddingList.push(item)
      const index = this.cardItemList.indexOf(item)
      this.cardItemList = this.cardItemList
        .slice(0, index)
        .concat(this.cardItemList.slice(index + 1))
      this.calcCover()
      this.calcValueList[item.val]++
      setTimeout(() => {
        item.style.top = '160%'
        item.style.left =
                    this.leftOffset + (this.penddingList.length - 1) * CardItem.x * 2 + 'px'
      }, 0)

      this.timer = setTimeout(() => {
        this.removeThree()
      }, 500)
    },
    // 开始
    startGame() {
      this.initGame()
    },
    // 设置
    setGame() {
      this.step = 0
    },
    // 重来
    rePlay() {
      this.initGame()
    }
  }
}
</script>

<style>
  .box {
    position: relative;
  }

  .intro {
    margin: 10% auto 0 auto;
    text-align: center;
  }

  .card-wrap {
    position: relative;
    margin: 10% auto 0 auto;
  }

  .card-item {
    font-size: 28px;
    text-align: center;
    position: absolute;
    border-radius: 2px;
    box-sizing: border-box;
    background: #ddd;
    opacity: 1;
    cursor: pointer;
    transition: all 0.3s;
    box-shadow: 0px 3px 0 0 #fff, 0 8px 0 0 #ddd, 0 8px 0 2px #333, 0 0 0 2px #333;
  }

  .card-item:hover {
    transform: scale3d(1.1, 1.1, 1.1);
    z-index: 1;
  }

  .item-cover {
    pointer-events: none;
    box-shadow: 0px 3px 0 0 #999, 0 8px 0 0 #666, 0 8px 0 2px #000, 0 0 0 2px #000;
  }

  .item-cover:after {
    border-radius: 2px;
    content: "";
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    background: #000;
    opacity: 0.55;
  }

  .card-tips {
    white-space: nowrap;
    position: absolute;
    left: 50%;
    top: 130%;
    transform: translate(-50%, 0);
    pointer-events: none;
  }

  .tools {
    position: absolute;
    top: 200%;
    width: 100%;
    left: 0;
    text-align: center;
  }

  .clear-item {
    pointer-events: none;
  }
</style>
